import 'dart:async';
import 'http_tool.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';

import 'package:flutter/services.dart';
export 'http_tool.dart';

class BlueCardResultData {
  final int code;
  final String desc;
  final Map dataInfo;
  final String sn;
  final String cardNo;
  final String cardGas;
  final String cardData;
  final List content;
  final List command;
  final String cardMemo;
  final String cardNumber;
  final String meterID;

  const BlueCardResultData({
    this.code,
    this.desc,
    this.dataInfo,
    this.sn,
    this.cardData,
    this.cardGas,
    this.cardNo,
    this.content,
    this.command,
    this.cardMemo,
    this.cardNumber,
    this.meterID,
  });

  static String checkNullToString(value) {
    return value != null ? value.toString() : '';
  }

  static fromMap(Map info) {
    return BlueCardResultData(code: info['code'], desc: info['desc']);
  }

  static error(
      String desc,
      String sn, {
        List content,
        List command,
      }) {
    return BlueCardResultData(
      desc: desc,
      sn: sn,
      code: -1,
      content: content,
      command: command,
    );
  }

  static fromReadCardRes(
      Map info,
      String sn,
      String cardData,
      ) {
    return BlueCardResultData(
      code: 0,
      desc: '读卡成功',
      sn: sn,
      dataInfo: info,
      cardNo: checkNullToString(info['cardNo']),
      cardGas: checkNullToString(info['cardGas']),
      cardNumber: checkNullToString(info['cardNumber']),
      meterID: checkNullToString(info['meterId']),
      cardData: cardData,
    );
  }
}

class FlutterBlueCardPlugin {
  static bool _connect = false;
  static bool get isConnecting => _connect;

  static const MethodChannel _channel =
  const MethodChannel('flutter_blue_card_plugin');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  static Future<BlueCardResultData> connect(String sn) async {
    final Map res = await _channel.invokeMethod('connect', {'sn': sn});
    BlueCardResultData data = BlueCardResultData.fromMap(res);
    if (data.code == 0) _connect = true;
    return data;
  }

  static Future<Map> sendData(data) async {
    final Map res =
    await _channel.invokeMethod('sendData', {'data': data.toString()});
    return res;
  }

  static Future<BlueCardResultData> readCard(
      String sn, {
        String ctrlCode = 'XA',
        String token,
        String arch,
      }) async {
    if (token == null || arch == null) {
      ResultData loginRes = await NetWorkManager.login(sn);
      if (!loginRes.success) {
        return BlueCardResultData.error(loginRes.desc, sn);
      }
      token = loginRes.data['token'];
      arch = loginRes.data['arch'];
    }
    String aseKey =
    md5.convert(utf8.encode(arch + sn)).toString().toLowerCase();
    print('aeskey : $aseKey');
    String password;
    if (ctrlCode == 'QC' || ctrlCode == 'SQKLQC') {
      ResultData resultData = await NetWorkManager.getPasswordCommand(
        sn,
        token,
        aseKey,
        ctrlCode: ctrlCode,
      );
      if (!resultData.success) {
        return BlueCardResultData.error(resultData.desc, sn);
      }
      List cmds = resultData.data['cmds'];
      List acks = resultData.data['acks'];
      List<String> results = [];
      for (int i = 0; i < cmds.length; ++i) {
        if (cmds[i].isEmpty) continue;
        Map info = await sendData(cmds[i]);
        if (info['code'] != 0) {
          return BlueCardResultData.error(
            '蓝牙读卡器发送指令失败${info['code']}',
            sn,
            content: [info['data']],
            command: [cmds[i]],
          );
        }
        if (!_compareAck(acks[i], info['data']))
          return BlueCardResultData.error(
            '读卡指令错误',
            sn,
            content: [info['data']],
            command: [cmds[i]],
          );
        results.add(info['data']);
      }
      password = results.last;
    }

    ///获取读卡指令集
    ResultData resultData = await NetWorkManager.getReadCommand(
      sn,
      token,
      aseKey,
      ctrlCode: ctrlCode,
      password: password,
    );
    if (!resultData.success) {
      return BlueCardResultData.error(resultData.desc, sn);
    }
    List cmds = resultData.data['cmds'];
    List acks = resultData.data['acks'];
    List<String> results = [];
    for (int i = 0; i < cmds.length; ++i) {
      if (cmds[i].isEmpty) continue;
      Map info = await sendData(cmds[i]);
      if (info['code'] != 0) {
        return BlueCardResultData.error(
          '蓝牙读卡器发送指令失败${info['code']}',
          sn,
          content: [info['data']],
          command: [cmds[i]],
        );
      }
      if (!_compareAck(acks[i], info['data']))
        return BlueCardResultData.error(
          '读卡指令错误',
          sn,
          content: [info['data']],
          command: [cmds[i]],
        );
      results.add(info['data']);
    }

    ///卡内容解析
    ResultData cardRes = await NetWorkManager.readCard(
      results.last,
      token,
      aseKey,
      ctrlCode: ctrlCode,
    );
    if (!cardRes.success) {
      return BlueCardResultData.error(
        '读卡失败' + cardRes.desc,
        sn,
        content: results,
        command: cmds,
      );
    }
    print('读卡信息：${cardRes.data}');
    return BlueCardResultData.fromReadCardRes(cardRes.data, sn, results.last);
  }

  static Future<BlueCardResultData> writeCard(
      String sn, {
        String ctrlCode = 'XA',
        String cardNo,
        String cardGas,
        String cardData,
        String cardMemo: '{}',
        String token,
        String arch,
        String cardNumber,
        String meterID,
      }) async {
    if (token == null || arch == null) {
      ResultData loginRes = await NetWorkManager.login(sn);
      if (!loginRes.success) {
        return BlueCardResultData.error(loginRes.desc, sn);
      }
      token = loginRes.data['token'];
      arch = loginRes.data['arch'];
    }
    String aseKey =
    md5.convert(utf8.encode(arch + sn)).toString().toLowerCase();
    print('aseKey : $aseKey');
    if (cardData.endsWith('9000')) {
      cardData = cardData.substring(0, cardData.length - 4);
    }
    ResultData resultData = await NetWorkManager.writeCard(
      aseKey,
      token,
      ctrlCode: ctrlCode,
      cardNo: cardNo,
      cardGas: cardGas,
      cardMemo: cardMemo,
      cardData: cardData,
      cardNumber: cardNumber,
      meterID: meterID,
    );
    if (!resultData.success) {
      return BlueCardResultData.error(
        resultData.desc,
        sn,
        content: [cardGas,cardMemo,cardNumber,ctrlCode,meterID],
        command: ['getWriteCommand'],
      );
    }
    List cmds = resultData.data['cmds'];
    List acks = resultData.data['acks'];
    List<String> results = [];
    for (int i = 0; i < cmds.length; ++i) {
      String cmd = cmds[i].toString();
      if (cmd.isEmpty) continue;
      Map info = await sendData(cmd);
      if (info['code'] != 0) {
        return BlueCardResultData.error(
          '蓝牙读卡器发送指令失败${info['code']}',
          sn,
          content: [info['data']],
          command: [cmds[i]],
        );
      }
      if (!_compareAck(acks[i], info['data']))
        return BlueCardResultData.error(
          '写卡指令错误',
          sn,
          content: [info['data']],
          command: [cmds[i]],
        );
      results.add(info['data']);
    }
    return BlueCardResultData(
      sn: sn,
      code: 0,
      desc: '写卡成功',
      cardGas: cardGas,
      cardNo: cardNo,
      content: results,
      command: cmds,
      cardMemo: resultData.data['cardMemo'],
    );
  }

  static Future<BlueCardResultData> disconnect() async {
    final Map res = await _channel.invokeMethod('disconnect');
    BlueCardResultData data = BlueCardResultData.fromMap(res);
    if (data.code == 0) _connect = false;
    return data;
  }

  static bool _compareAck(String ack, String result) {
    result = result.toUpperCase();
    if (ack.length != result.length) return false;
    for (int i = 0; i < ack.length; ++i) {
      if (ack[i] != 'F' && ack[i] != result[i]) return false;
    }
    return true;
  }

  static Future<BlueCardResultData> checkBluetoothSn(String sn) async {
    ResultData loginRes = await NetWorkManager.login(sn);
    if (!loginRes.success) {
      return BlueCardResultData.error(loginRes.desc, sn);
    }
    return BlueCardResultData(code: 0);
  }
}
